/*
 * Copyright 2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.internal.process;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Pattern;

public class ArgWriter implements ArgCollector {

    private static final Pattern WHITESPACE = Pattern.compile("\\s");
    private static final Pattern WHITESPACE_OR_HASH = Pattern.compile("\\s|#");

    private final PrintWriter writer;
    private final boolean backslashEscape;
    private final Pattern quotablePattern;

    private ArgWriter(PrintWriter writer, boolean backslashEscape, Pattern quotablePattern) {
        this.writer = writer;
        this.backslashEscape = backslashEscape;
        this.quotablePattern = quotablePattern;
    }

    /**
     * Double quotes around args containing whitespace, backslash chars are escaped using double backslash, platform line separators.
     */
    public static ArgWriter unixStyle(PrintWriter writer) {
        return new ArgWriter(writer, true, WHITESPACE);
    }

    public static Function<PrintWriter, ArgWriter> unixStyleFactory() {
        return ArgWriter::unixStyle;
    }

    /**
     * Double quotes around args containing whitespace or #, backslash chars are escaped using double backslash, platform line separators.
     *
     * See <a href='https://docs.oracle.com/javase/9/tools/java.htm#JSWOR-GUID-4856361B-8BFD-4964-AE84-121F5F6CF111'>java Command-Line Argument Files</a>.
     */
    public static ArgWriter javaStyle(PrintWriter writer) {
        return new ArgWriter(writer, true, WHITESPACE_OR_HASH);
    }

    public static Function<PrintWriter, ArgWriter> javaStyleFactory() {
        return ArgWriter::javaStyle;
    }

    /**
     * Double quotes around args containing whitespace, platform line separators.
     */
    public static ArgWriter windowsStyle(PrintWriter writer) {
        return new ArgWriter(writer, false, WHITESPACE);
    }

    public static Function<PrintWriter, ArgWriter> windowsStyleFactory() {
        return ArgWriter::windowsStyle;
    }

    /**
     * Returns an args transformer that replaces the provided args with a generated args file containing the args. Uses platform text encoding.
     */
    public static Function<List<String>, List<String>> argsFileGenerator(final File argsFile, final Function<PrintWriter, ArgWriter> argWriterFactory) {
        return args -> generateArgsFile(argsFile, argWriterFactory, args);
    }

    private static List<String> generateArgsFile(File argsFile, Function<PrintWriter, ArgWriter> argWriterFactory, List<String> args) {
        if (args.isEmpty()) {
            return args;
        }
        argsFile.getParentFile().mkdirs();
        try {
            // TODO(https://github.com/gradle/gradle/issues/29303)
            @SuppressWarnings("DefaultCharset") // This method is documented as "uses platform text encoding"
            PrintWriter writer = new PrintWriter(argsFile);
            try {
                ArgWriter argWriter = argWriterFactory.apply(writer);
                argWriter.args(args);
            } finally {
                writer.close();
            }
        } catch (IOException e) {
            throw new UncheckedIOException(String.format("Could not write options file '%s'.", argsFile.getAbsolutePath()), e);
        }
        return Collections.singletonList("@" + argsFile.getAbsolutePath());
    }

    /**
     * Writes a set of args on a single line, escaping and quoting as required.
     */
    @Override
    public ArgWriter args(Object... args) {
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (i > 0) {
                writer.print(' ');
            }
            String str = arg.toString();
            if (backslashEscape) {
                str = str.replace("\\", "\\\\").replace("\"", "\\\"");
            }
            if (str.isEmpty()) {
                writer.print("\"\"");
            } else if (needsQuoting(str)) {
                writer.print('\"');
                writer.print(str);
                writer.print('\"');
            } else {
                writer.print(str);
            }
        }
        writer.println();
        return this;
    }

    private boolean needsQuoting(String str) {
        return quotablePattern.matcher(str).find();
    }

    @Override
    public ArgCollector args(Iterable<?> args) {
        for (Object arg : args) {
            args(arg);
        }
        return this;
    }
}
